package com.demo.config;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.hibernate.validator.internal.engine.ValidatorImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import org.springframework.web.servlet.support.RequestContextUtils;

//TODO 需要重新
// 注意：此处只支持@RequetMapping方式并且方法上有Validated注解
// Controller的Bean类型需要参数上加上Valid注解或者Validated注解 如果是@requestbody 那么是MethodArgumentNotValidException否则是BindException
public class ValidationInterceptor implements HandlerInterceptor, InitializingBean {

    private static Logger log = LoggerFactory.getLogger(ValidationInterceptor.class);

    @Autowired
    private LocalValidatorFactoryBean validatorFactoryBean;

    @Autowired
    private RequestMappingHandlerAdapter adapter;

    private List<HandlerMethodArgumentResolver> argumentResolvers;

    @Override
    public void afterPropertiesSet() throws Exception {
        argumentResolvers = adapter.getArgumentResolvers();
    }

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
        new ConcurrentHashMap<>(256);

    private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        // 只处理HandlerMethod方式
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod)handler;
            Validated valid = method.getMethodAnnotation(Validated.class); //
            if (valid != null) {
                // 根据工厂，拿到一个校验器
                ValidatorImpl validatorImpl = (ValidatorImpl)validatorFactoryBean.getValidator();

                // 拿到该方法所有的参数们~~~ org.springframework.core.MethodParameter
                MethodParameter[] parameters = method.getMethodParameters();
                Object[] parameterValues = new Object[parameters.length];

                WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(method);
                ModelAndViewContainer mavContainer = new ModelAndViewContainer();
                mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
                ServletWebRequest swr = new ServletWebRequest(request, response);
                ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

                // 遍历所有的入参：给每个参数做赋值和数据绑定
                for (int i = 0; i < parameters.length; i++) {
                    MethodParameter parameter = parameters[i];
                    parameter.initParameterNameDiscovery(parameterNameDiscoverer);

                    // 找到适合解析这个参数的处理器~
                    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
                    Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
                    Object value = resolver.resolveArgument(parameter, mavContainer, swr, webDataBinderFactory);
                    parameterValues[i] = value; // 赋值
                }

//                // 对入参进行统一校验
                Set<ConstraintViolation<Object>> violations = validatorImpl.validateParameters(method.getBean(),
                    method.getMethod(), parameterValues, valid.value());
                // 若存在错误消息，此处也做抛出异常处理 javax.validation.ConstraintViolationException
                if (!violations.isEmpty()) {
                    log.error("方法入参校验失败~~~~~~~");
                    throw new ConstraintViolationException(violations);
                }
            }

        }

        return true;
    }

    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) {
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) {
            // 支持到@InitBinder注解
            methods = MethodIntrospector.selectMethods(handlerType, RequestMappingHandlerAdapter.INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(new InvocableHandlerMethod(bean, method));
        }
        return new ServletRequestDataBinderFactory(initBinderMethods, adapter.getWebBindingInitializer());
    }

    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {}

}
